# pita: An invisible audio player
# Copyright (C) 2006-2007 Austin Bingham
#
# This program is free software# you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation# either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY# without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program# if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# You can reach the author at:
#     austin.bingham@gmail.com

import cPickle, logging, os, os.path, sys, time, urllib, xmlrpclib
import pita.playlist as pl
import pita.log as log
import pita

ProxyError = 'Unable to establish server proxy'

logger = logging.getLogger('pita.client_util')

def net_to_playlist(in_pl):
    '''
    This converts the xmlrpc-ified format of a
    Playlist back into a regular Playlist object
    '''
    return cPickle.loads(in_pl)

def fix_path(path):
    '''
    Given a path, returns an absolute version of that path
    with user expansion performed. If the path appears to be
    an http url, do not change it.
    '''
    if not path.lower().startswith('http://'):
        return os.path.abspath(os.path.expanduser(path))
    else:
        return path

def walk_paths(paths):
    """
    Takes in a list of paths. For each path in 'paths', returns a
    list containing:
    - If 'path' is not a directory, that path unchanged
    - If 'path' is a directory, all files under (recursively)
      'path'
    """
    rval = []
    for p in paths:
        if os.path.isdir(p):
            for root,dirs,files in os.walk(p):
                for file in files:
                    rval.append(os.path.join(root, file))
        else:
            rval.append(p)
    rval.sort()
    return rval

def fix_and_test_paths(paths):
    """
    Takes in a list of paths. For every file in 'paths' and for all
    files under directories in 'paths' the file is fully
    expanded. If this expanded path can be read (i.e. it
    exists and we have read permission on it) it is put on a
    'good' list; otherwise, it is put on a 'bad' list. The return
    value is a tuple (<good list>, <bad list>)
    """
    # first, expand any directories
    paths = walk_paths(paths)

    good = []
    bad = []
    for p in paths:
        try:
            fixed = fix_path(p)
            urllib.urlopen(fixed) # test url by opening it
            good.append(fixed)            
        except IOError:
            bad.append(p)
        except UnicodeDecodeError:
            bad.append(p)
            
    return (good,bad)

class StringSafeServerProxy(xmlrpclib.ServerProxy):
    '''
    This class is designed to make it safe to pass arbitrary strings
    to some function on our server. The problem is that some filenames
    contain characters that can not safely be passed over xmlrpc due
    to encoding issues. These strings can, however, be wrapper in xmlrpc.Binary
    objects and then safely passed. That wrapping is what this class
    does. Of course, unwrapping needs to be done on the server side.
    '''
    def __init__(self, address):
        xmlrpclib.ServerProxy.__init__(self, address)

    def append(self, files):
        # xmlrpclib.ServerProxy.append(self, [xmlrpclib.Binary(f) for f in files])
        xmlrpclib.ServerProxy.__getattr__(self, "append")([xmlrpclib.Binary(f) for f in files])

def locate_existing_server(host, port):
    proxy = StringSafeServerProxy('http://%s:%d' % (host, port))
    proxy.ping()
    return proxy
    
def get_server_proxy(start_new=False,
                     sleep_time=1, retry_attempts=3):
    """
    This attempts to find or, optionally, start a new server. First,
    this tries to contact an existing server at the configured host
    and port. If this fails AND if start_new is True, this tries to
    start a new server at the configured host/port. In any event,
    the throws ProxyError if there is no usable proxy to return (i.e.
    one could neither be contacted or created).

    sleep_time controls how long after the creation of a new server
    process this function will wait before trying to contact the
    server. This is a bit of a hack designed to account for the
    fact that we can't otherwise know when the server is 'ready'.

    retry_attempts controls how many times this will try to contact
    the newly created server (with a wait of sleep_time between
    each attempt.)
    """
    # First, try to connect to an existing server
    try:
        logger.info('looking for existing server on port %d' % pita.conf.port)
        proxy = locate_existing_server(pita.conf.host, pita.conf.port)
        logger.info('existing server found on port %d' % pita.conf.port)
        return proxy
    except:
        logger.info('could not find existing server on port %d' % pita.conf.port)

    # if we couldn't find an existing server but were told not
    # to start one, crap out
    if not start_new: raise ProxyError

    # start a new server
    if os.fork() == 0:
        logger.info('starting new server on port %d' % pita.conf.port)
        # os.spawnlp(os.P_DETACH, 'pitad', 'pitad', '--port', str(options.port))
        os.system('pitad --port %d &' % pita.conf.port)
    else:
        time.sleep(sleep_time) # KLUDGE: give the server some time to start up
        for retry in range(retry_attempts):
            try:
                return locate_existing_server(pita.conf.host, pita.conf.port)
            except:
                info = sys.exc_info()
                logger.info('exception while locating existing server: %s %s' % (info[0], info[1]))
            time.sleep(sleep_time)

    raise ProxyError

def get_server_proxy_or_die(start_new=False,
                            sleep_time=1, retry_attempts=3):
    try:
        return get_server_proxy(start_new,
                                sleep_time, retry_attempts)
    except ProxyError:
        logger.error('Unable to get server proxy')
        sys.exit(1)

